﻿using System;
using System.Collections;
using System.Collections.Generic;
using Silver3D.Math;

namespace Silver3D.Graphics
{
    /// <summary>
    /// A node in a directed acyclic scene graph (DAG).
    /// Supports multiple parents (node instantiation), instances and transforms.
    /// </summary>
    public class Node : Entity
    {
        private List<Node> parents = new List<Node>();
        private List<Node> children = new List<Node>();
        private List<EntityInstance> instances = new List<EntityInstance>();
        private List<Transform> transforms = new List<Transform>();

        /// <summary>
        /// Node constructor.
        /// </summary>
        /// <param name="scene">The scene that owns this Node.</param>
        /// <param name="id">The desired ID for this Node.</param>
        public Node(Scene scene, string id)
            : base(scene, id)
        {
        }

        #region Parents

        /// <summary>
        /// Returns the first parent in this node's parent list.
        /// Will return null if the node has no parent.
        /// </summary>
        public Node Parent
        {
            get
            {
                if (parents.Count == 0) return null;
                return parents[0];
            }
        }

        /// <summary>
        /// Returns the number of parents for this Node.
        /// </summary>
        public int ParentCount
        {
            get { return parents.Count; }
        }

        /// <summary>
        /// Returns the first parent of this Node, null if this Node has no parent.
        /// </summary>
        public Node FirstParent
        {
            get
            {
                if (parents.Count > 0) return parents[0];
                return null;
            }
        }

        /// <summary>
        /// Returns the Node parent specified by the given index.
        /// </summary>
        /// <param name="index">Must be lower than ParentCount.</param>
        /// <returns>The parent Node.</returns>
        public Node GetParent(int index)
        {
            return parents[index];
        }

        #endregion // Parents

        #region Children

        /// <summary>
        /// Returns the number of child Nodes for this Node.
        /// </summary>
        public int ChildCount
        {
            get { return children.Count; }
        }

        /// <summary>
        /// Returns the Node child specified by the given index.
        /// </summary>
        /// <param name="index">Must be lower than ChildCount.</param>
        /// <returns>The child Node.</returns>
        public Node GetChild(int index)
        {
            return children[index];
        }

        /// <summary>
        /// Returns the first child of this Node, or null if this Node has no child.
        /// </summary>
        public Node FirstChild
        {
            get
            {
                if (children.Count == 0) return null;
                return children[0];
            }
        }

        /// <summary>
        /// Adds the given child node to the children list of this node.
        /// Cycle detection is performed by this method, and if adding this child Node
        /// results in a cycle in the scene graph the method returns False.
        /// </summary>
        /// <param name="child">The new child.</param>
        /// <returns>True if the operation succeeds, False otherwise.</returns>
        public bool AddChild(Node child)
        {
            if (child == null)
                throw new ArgumentNullException("child");

            if (children.Contains(child))
                return false;

            // return false if adding the child would introduce a cycle in the scene graph
            if (child == this) return false;
            if (parents.Contains(child)) return false;
            Queue<Node> queue = new Queue<Node>(parents);
            while (queue.Count > 0)
            {
                Node p = queue.Dequeue();
                if (child == p) return false;
                foreach (Node n in p.parents) queue.Enqueue(n);
            }

            child.parents.Add(this);
            children.Add(child);
            child.Detached += new ObjectDetachedHandler(OnChildDetached);
            return true;
        }

        /// <summary>
        /// Creates and adds a new child to this Node.
        /// </summary>
        /// <returns>The new child Node.</returns>
        public Node AddChild()
        {
            return AddChild(this.Id + "Child");
        }

        /// <summary>
        /// Creates and adds a new child to this Node.
        /// </summary>
        /// <param name="id">The desired ID of the new child Node.</param>
        /// <returns>The new child Node.</returns>
        public Node AddChild(string id)
        {
            Node n = new Node(Scene, id);
            AddChild(n);
            return n;
        }

        /// <summary>
        /// Removes a child Node without detaching it from the Scene.
        /// You can then add this Node to another Node in the graph.
        /// </summary>
        /// <param name="child">The child Node to remove.</param>
        /// <returns>True if the provided Node is a child of this Node, False otherwise.</returns>
        public bool RemoveChild(Node child)
        {
            if (child == null)
                throw new ArgumentNullException("child");

            if (children.Contains(child))
            {
                children.Remove(child);
                child.parents.Remove(this);
                child.Detached -= new ObjectDetachedHandler(OnChildDetached);
                return true;
            }
            return false;
        }

        private void OnChildDetached(SceneObject obj)
        {
            Node child = obj as Node;
            if (child == null) return;
            child.parents.Remove(this);
            children.Remove(child);
        }

        #endregion // Children

        #region Instances

        /// <summary>
        /// Returns the number of EntityInstances attached to this Node.
        /// </summary>
        public int InstanceCount
        {
            get { return instances.Count; }
        }

        /// <summary>
        /// Returns the EntityInstance specified by the given index.
        /// </summary>
        /// <param name="index">Must be lower than InstanceCount.</param>
        /// <returns>The EntityInstance.</returns>
        public EntityInstance GetInstance(int index)
        {
            return instances[index];
        }

        /// <summary>
        /// Adds the given instance to this Node.
        /// </summary>
        /// <param name="instance">The instance to add.</param>
        /// <returns>True on success, False otherwise.</returns>
        public bool AddInstance(EntityInstance instance)
        {
            if (instance == null)
                throw new ArgumentNullException("instance");

            if (instance.Parent == null)
            {
                instance.Parent = this;
                instances.Add(instance);
                return true;
            }
            return false;
        }

        /// <summary>
        /// Removes the given instance from this Node.
        /// </summary>
        /// <param name="instance">The instance to remove.</param>
        /// <returns>True on success, False otherwise.</returns>
        public bool RemoveInstance(EntityInstance instance)
        {
            if (instance == null)
                throw new ArgumentNullException("instance");

            if (instance.Parent == this)
            {
                instance.Parent = null;
                return instances.Remove(instance);
            }
            return false;
        }

        #endregion // Instances

        #region Transforms

        public int TransformCount
        {
            get { return transforms.Count; }
        }

        public Transform GetTransform(int index)
        {
            return transforms[index];
        }

        public bool AddTransform(Transform transform)
        {
            if (transform == null)
                throw new ArgumentNullException("transform");

            if (transform.Parent == null)
            {
                transform.Parent = this;
                transforms.Add(transform);
                return true;
            }
            return false;
        }

        public bool RemoveTransform(Transform transform)
        {
            if (transform == null)
                throw new ArgumentNullException("transform");

            if (transform.Parent == this)
            {
                transform.Parent = null;
                return transforms.Remove(transform);
            }
            return false;
        }

        /// <summary>
        /// Computes the local transformation for this node by multiplying
        /// its transforms.
        /// </summary>
        /// <returns>The local transformation.</returns>
        public Matrix44 ToMatrix()
        {
            Matrix44 r = Matrix44.Identity;
            foreach (Transform t in transforms)
                r *= t.ToMatrix();
            return r;
        }

        #endregion // Transforms

        /// <summary>
        /// Detaches this Node's children, effectively detaching all of this Node' sub-tree.
        /// The Node's instances and transforms are also detached.
        /// </summary>
        public override void Detach()
        {
            // remove all children from the graph (depth first)
            int expectedChildCount = children.Count;
            while (expectedChildCount-- > 0)
            {
                // only detach if we're the only parent!
                Node c = children[0];
                if (c.ParentCount == 1) c.Detach();
                else RemoveChild(c);
            }

            if (children.Count > 0)
                throw new Silver3DException("Failed to detach this Node's chilren. {0} children remaining.", children.Count);

            // remove all instances attached to this Node
            int expectedInstancesCount = instances.Count;
            while (expectedInstancesCount-- > 0)
                instances[0].Detach();

            if (instances.Count > 0)
                throw new Silver3DException("Failed to detach this Node's instances. {0} instances remaining.", instances.Count);

            int expectedTransformsCount = transforms.Count;
            while (expectedTransformsCount-- > 0)
                transforms[0].Detach();

            if (transforms.Count > 0)
                throw new Silver3DException("Failed to detach this Node's transforms. {0} transforms remaining.", transforms.Count);

            // detach itself
            base.Detach();

            // should be cleared already
            if (parents.Count > 0)
                throw new Silver3DException("Failed to notify Detach action to this Node's parents. {0} parents remaining.", parents.Count);
        }
    }
}
